<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>视频帧提取与帧动画生成工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            color: #fff;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: rgba(0, 0, 0, 0.7);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding-bottom: 15px;
            border-bottom: 2px solid rgba(255, 255, 255, 0.2);
        }
        
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }
        
        .subtitle {
            font-size: 1.1rem;
            opacity: 0.8;
        }
        
        .main-content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 25px;
        }
        
        @media (max-width: 1100px) {
            .main-content {
                grid-template-columns: 1fr;
            }
        }
        
        .panel {
            background: rgba(30, 30, 40, 0.8);
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        .panel-title {
            font-size: 1.3rem;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.2);
            display: flex;
            align-items: center;
        }
        
        .panel-title i {
            margin-right: 10px;
            color: #fdbb2d;
        }
        
        .video-input-section {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        
        .input-group {
            display: flex;
            gap: 10px;
        }
        
        input, button, select {
            padding: 12px 15px;
            border: none;
            border-radius: 5px;
            font-size: 1rem;
        }
        
        input {
            flex: 1;
            background: rgba(255, 255, 255, 0.1);
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.2);
        }
        
        input::placeholder {
            color: rgba(255, 255, 255, 0.5);
        }
        
        button {
            background: linear-gradient(to right, #3498db, #2ecc71);
            color: white;
            cursor: pointer;
            transition: all 0.3s;
            font-weight: bold;
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        button:active {
            transform: translateY(0);
        }
        
        .drop-area {
            border: 2px dashed rgba(255, 255, 255, 0.3);
            border-radius: 8px;
            padding: 30px;
            text-align: center;
            transition: all 0.3s;
            cursor: pointer;
        }
        
        .drop-area:hover, .drop-area.dragover {
            border-color: #2ecc71;
            background: rgba(46, 204, 113, 0.1);
        }
        
        .video-preview {
            width: 100%;
            height: 300px;
            background: #000;
            border-radius: 8px;
            overflow: hidden;
            margin-bottom: 15px;
            position: relative;
        }
        
        #videoPlayer {
            width: 100%;
            height: 100%;
            object-fit: contain;
        }
        
        .timeline-controls {
            margin: 15px 0;
        }
        
        .timeline {
            width: 100%;
            height: 40px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 5px;
            position: relative;
            margin-bottom: 10px;
            cursor: pointer;
        }
        
        .timeline-progress {
            position: absolute;
            height: 100%;
            background: linear-gradient(to right, #3498db, #2ecc71);
            border-radius: 5px;
            width: 0%;
        }
        
        .timeline-handle {
            position: absolute;
            width: 15px;
            height: 15px;
            background: white;
            border-radius: 50%;
            top: 50%;
            transform: translateY(-50%);
            cursor: grab;
            box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
        }
        
        .timeline-handle:active {
            cursor: grabbing;
        }
        
        .time-display {
            display: flex;
            justify-content: space-between;
            font-size: 0.9rem;
            opacity: 0.8;
        }
        
        .frame-controls {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            margin-top: 15px;
        }
        
        .frames-preview {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            max-height: 300px;
            overflow-y: auto;
            padding: 10px;
            background: rgba(0, 0, 0, 0.3);
            border-radius: 8px;
        }
        
        .frame-item {
            width: 100px;
            height: 80px;
            border-radius: 5px;
            overflow: hidden;
            position: relative;
            cursor: pointer;
            border: 2px solid transparent;
            transition: all 0.2s;
        }
        
        .frame-item:hover {
            transform: scale(1.05);
        }
        
        .frame-item.selected {
            border-color: #2ecc71;
        }
        
        .frame-checkbox {
            position: absolute;
            top: 5px;
            right: 5px;
            width: 20px;
            height: 20px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .frame-checkbox.checked {
            background: #2ecc71;
        }
        
        .frame-checkbox.checked::after {
            content: "✓";
            color: white;
            font-size: 14px;
        }
        
        .animation-preview {
            width: 100%;
            height: 200px;
            background: #000;
            border-radius: 8px;
            margin: 15px 0;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
        }
        
        #animationCanvas {
            max-width: 100%;
            max-height: 100%;
        }
        
        .animation-controls {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }
        
        .speed-control {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .export-section {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 20px;
            padding-top: 15px;
            border-top: 1px solid rgba(255, 255, 255, 0.2);
        }
        
        .status {
            padding: 10px;
            border-radius: 5px;
            margin-top: 10px;
            text-align: center;
            display: none;
        }
        
        .status.success {
            background: rgba(46, 204, 113, 0.2);
            border: 1px solid #2ecc71;
            display: block;
        }
        
        .status.error {
            background: rgba(231, 76, 60, 0.2);
            border: 1px solid #e74c3c;
            display: block;
        }
        
        .icon {
            margin-right: 8px;
        }
        
        footer {
            text-align: center;
            margin-top: 30px;
            padding-top: 15px;
            border-top: 1px solid rgba(255, 255, 255, 0.2);
            font-size: 0.9rem;
            opacity: 0.7;
        }
        
        .frame-actions {
            display: flex;
            gap: 10px;
            margin-top: 15px;
        }
        
        .frame-actions button {
            flex: 1;
            padding: 10px;
            font-size: 0.9rem;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>视频帧提取与帧动画生成工具</h1>
            <p class="subtitle">提取视频帧，创建流畅的帧动画，支持本地视频和网络视频</p>
        </header>
        
        <div class="video-input-section">
            <div class="input-group">
                <input type="text" id="videoUrl" placeholder="输入视频URL或选择本地视频文件">
                <button id="loadVideoBtn">加载视频</button>
            </div>
            
            <div class="drop-area" id="dropArea">
                <p>拖拽视频文件到此处，或点击选择文件</p>
                <input type="file" id="fileInput" accept="video/*" style="display: none;">
            </div>
        </div>
        
        <div class="main-content">
            <div class="left-panel">
                <div class="panel">
                    <h2 class="panel-title"><span class="icon">🎬</span>视频预览与帧提取</h2>
                    
                    <div class="video-preview">
                        <video id="videoPlayer" controls></video>
                    </div>
                    
                    <div class="timeline-controls">
                        <div class="timeline" id="timeline">
                            <div class="timeline-progress" id="timelineProgress"></div>
                            <div class="timeline-handle" id="startHandle"></div>
                            <div class="timeline-handle" id="endHandle"></div>
                        </div>
                        <div class="time-display">
                            <span id="startTime">00:00</span>
                            <span id="endTime">00:00</span>
                        </div>
                    </div>
                    
                    <div class="frame-controls">
                        <input type="number" id="frameInterval" min="1" max="1000" value="100" placeholder="帧间隔(ms)">
                        <button id="extractFramesBtn">提取视频帧</button>
                    </div>
                    
                    <div id="extractionStatus" class="status"></div>
                </div>
                
                <div class="panel">
                    <h2 class="panel-title"><span class="icon">🖼️</span>帧预览与选择</h2>
                    
                    <div class="frames-preview" id="framesContainer">
                        <!-- 提取的帧将在这里显示 -->
                    </div>
                    
                    <div class="frame-actions">
                        <button id="selectAllBtn">全选</button>
                        <button id="deselectAllBtn">取消全选</button>
                        <button id="clearFramesBtn">清除所有帧</button>
                    </div>
                </div>
            </div>
            
            <div class="right-panel">
                <div class="panel">
                    <h2 class="panel-title"><span class="icon">🎞️</span>帧动画预览</h2>
                    
                    <div class="animation-preview">
                        <canvas id="animationCanvas"></canvas>
                    </div>
                    
                    <div class="animation-controls">
                        <button id="playAnimationBtn">播放动画</button>
                        <button id="stopAnimationBtn">停止动画</button>
                        <div class="speed-control">
                            <label for="animationSpeed">播放速度:</label>
                            <select id="animationSpeed">
                                <option value="0.25">0.25x</option>
                                <option value="0.5">0.5x</option>
                                <option value="1" selected>1x</option>
                                <option value="1.5">1.5x</option>
                                <option value="2">2x</option>
                            </select>
                        </div>
                    </div>
                </div>
                
                <div class="panel">
                    <h2 class="panel-title"><span class="icon">📤</span>导出帧动画</h2>
                    
                    <div class="export-section">
                        <div>
                            <input type="text" id="exportPrefix" placeholder="文件名前缀" value="frame">
                            <span>已选择 <span id="selectedCount">0</span> 帧</span>
                        </div>
                        <button id="exportFramesBtn">导出选中帧</button>
                    </div>
                    
                    <div id="exportStatus" class="status"></div>
                </div>
            </div>
        </div>
        
        <footer>
            <p>视频帧提取工具 &copy; 2023 | 使用Canvas技术实现</p>
        </footer>
    </div>

    <script type="module">
        // 视频帧提取模块
        class VideoFrameExtractor {
            constructor() {
                this.canvas = document.createElement('canvas');
                this.ctx = this.canvas.getContext('2d');
            }
            
            async extractFrames(video, startTime, endTime, interval) {
                return new Promise((resolve, reject) => {
                    try {
                        const frames = [];
                        const duration = endTime - startTime;
                        const frameCount = Math.floor(duration * 1000 / interval);
                        
                        // 设置canvas尺寸与视频一致
                        this.canvas.width = video.videoWidth;
                        this.canvas.height = video.videoHeight;
                        
                        let currentTime = startTime;
                        let extractedCount = 0;
                        
                        const extractFrame = () => {
                            if (currentTime > endTime || extractedCount >= frameCount) {
                                resolve(frames);
                                return;
                            }
                            
                            video.currentTime = currentTime;
                            
                            // 等待视频跳转到指定时间
                            const onSeeked = () => {
                                video.removeEventListener('seeked', onSeeked);
                                
                                // 绘制当前帧到canvas
                                this.ctx.drawImage(video, 0, 0, this.canvas.width, this.canvas.height);
                                
                                // 将canvas内容转换为数据URL
                                const dataUrl = this.canvas.toDataURL('image/png');
                                frames.push(dataUrl);
                                
                                extractedCount++;
                                currentTime += interval / 1000;
                                
                                // 继续提取下一帧
                                setTimeout(extractFrame, 0);
                            };
                            
                            video.addEventListener('seeked', onSeeked);
                        };
                        
                        extractFrame();
                    } catch (error) {
                        reject(error);
                    }
                });
            }
        }
        
        // 帧动画模块
        class FrameAnimation {
            constructor(canvas) {
                this.canvas = canvas;
                this.ctx = canvas.getContext('2d');
                this.frames = [];
                this.currentFrame = 0;
                this.isPlaying = false;
                this.animationId = null;
                this.speed = 1;
            }
            
            play(frames, speed = 1) {
                this.frames = frames;
                this.speed = speed;
                this.currentFrame = 0;
                this.isPlaying = true;
                
                // 设置canvas尺寸
                if (frames.length > 0) {
                    const img = new Image();
                    img.onload = () => {
                        this.canvas.width = img.width;
                        this.canvas.height = img.height;
                        this.animate();
                    };
                    img.src = frames[0];
                }
            }
            
            stop() {
                this.isPlaying = false;
                if (this.animationId) {
                    cancelAnimationFrame(this.animationId);
                    this.animationId = null;
                }
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            }
            
            animate() {
                if (!this.isPlaying || this.frames.length === 0) return;
                
                // 绘制当前帧
                const img = new Image();
                img.onload = () => {
                    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                    this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
                };
                img.src = this.frames[this.currentFrame];
                
                // 更新到下一帧
                this.currentFrame = (this.currentFrame + 1) % this.frames.length;
                
                // 根据速度计算下一帧的时间
                const delay = Math.max(1000 / (12 * this.speed), 50); // 限制最小延迟
                
                // 使用setTimeout而不是requestAnimationFrame以控制速度
                setTimeout(() => {
                    this.animate();
                }, delay);
            }
        }
        
        // 帧导出模块
        class FrameExporter {
            exportFrames(frames, prefix = 'frame') {
                if (!frames || frames.length === 0) {
                    throw new Error('没有可导出的帧');
                }
                
                frames.forEach((frameData, index) => {
                    const link = document.createElement('a');
                    link.download = `${prefix}_${index + 1}.png`;
                    link.href = frameData.dataUrl;
                    link.click();
                });
            }
        }
        
        // 初始化应用
        document.addEventListener('DOMContentLoaded', () => {
            const app = new VideoFrameApp();
            app.init();
        });
        
        class VideoFrameApp {
            constructor() {
                this.videoPlayer = document.getElementById('videoPlayer');
                this.videoUrlInput = document.getElementById('videoUrl');
                this.loadVideoBtn = document.getElementById('loadVideoBtn');
                this.dropArea = document.getElementById('dropArea');
                this.fileInput = document.getElementById('fileInput');
                this.timeline = document.getElementById('timeline');
                this.timelineProgress = document.getElementById('timelineProgress');
                this.startHandle = document.getElementById('startHandle');
                this.endHandle = document.getElementById('endHandle');
                this.startTime = document.getElementById('startTime');
                this.endTime = document.getElementById('endTime');
                this.frameInterval = document.getElementById('frameInterval');
                this.extractFramesBtn = document.getElementById('extractFramesBtn');
                this.extractionStatus = document.getElementById('extractionStatus');
                this.framesContainer = document.getElementById('framesContainer');
                this.selectAllBtn = document.getElementById('selectAllBtn');
                this.deselectAllBtn = document.getElementById('deselectAllBtn');
                this.clearFramesBtn = document.getElementById('clearFramesBtn');
                this.animationCanvas = document.getElementById('animationCanvas');
                this.playAnimationBtn = document.getElementById('playAnimationBtn');
                this.stopAnimationBtn = document.getElementById('stopAnimationBtn');
                this.animationSpeed = document.getElementById('animationSpeed');
                this.exportPrefix = document.getElementById('exportPrefix');
                this.selectedCount = document.getElementById('selectedCount');
                this.exportFramesBtn = document.getElementById('exportFramesBtn');
                this.exportStatus = document.getElementById('exportStatus');
                
                this.extractedFrames = [];
                this.selectedFrames = [];
                this.startTimeValue = 0;
                this.endTimeValue = 0;
                
                // 初始化模块
                this.frameExtractor = new VideoFrameExtractor();
                this.frameAnimation = new FrameAnimation(this.animationCanvas);
                this.frameExporter = new FrameExporter();
            }
            
            init() {
                this.setupEventListeners();
                this.setupTimelineControls();
            }
            
            setupEventListeners() {
                // 视频加载
                this.loadVideoBtn.addEventListener('click', () => {
                    this.loadVideoFromUrl();
                });
                
                // 文件选择
                this.dropArea.addEventListener('click', () => {
                    this.fileInput.click();
                });
                
                this.fileInput.addEventListener('change', (e) => {
                    this.loadVideoFromFile(e.target.files[0]);
                });
                
                // 拖拽上传
                this.dropArea.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    this.dropArea.classList.add('dragover');
                });
                
                this.dropArea.addEventListener('dragleave', () => {
                    this.dropArea.classList.remove('dragover');
                });
                
                this.dropArea.addEventListener('drop', (e) => {
                    e.preventDefault();
                    this.dropArea.classList.remove('dragover');
                    
                    if (e.dataTransfer.files.length) {
                        this.loadVideoFromFile(e.dataTransfer.files[0]);
                    }
                });
                
                // 视频播放时间更新
                this.videoPlayer.addEventListener('timeupdate', () => {
                    this.updateTimelineProgress();
                });
                
                // 帧提取
                this.extractFramesBtn.addEventListener('click', () => {
                    this.extractFrames();
                });
                
                // 帧选择
                this.selectAllBtn.addEventListener('click', () => {
                    this.selectAllFrames();
                });
                
                this.deselectAllBtn.addEventListener('click', () => {
                    this.deselectAllFrames();
                });
                
                this.clearFramesBtn.addEventListener('click', () => {
                    this.clearAllFrames();
                });
                
                // 动画控制
                this.playAnimationBtn.addEventListener('click', () => {
                    this.playAnimation();
                });
                
                this.stopAnimationBtn.addEventListener('click', () => {
                    this.stopAnimation();
                });
                
                // 导出
                this.exportFramesBtn.addEventListener('click', () => {
                    this.exportFrames();
                });
            }
            
            setupTimelineControls() {
                let isDragging = false;
                let activeHandle = null;
                
                const updateTimeValues = () => {
                    const timelineRect = this.timeline.getBoundingClientRect();
                    const timelineWidth = timelineRect.width;
                    
                    const startPercent = (parseInt(this.startHandle.style.left) || 0) / timelineWidth;
                    const endPercent = (parseInt(this.endHandle.style.left) || timelineWidth) / timelineWidth;
                    
                    if (this.videoPlayer.duration) {
                        this.startTimeValue = startPercent * this.videoPlayer.duration;
                        this.endTimeValue = endPercent * this.videoPlayer.duration;
                        
                        this.startTime.textContent = this.formatTime(this.startTimeValue);
                        this.endTime.textContent = this.formatTime(this.endTimeValue);
                    }
                };
                
                const onMouseDown = (e, handle) => {
                    isDragging = true;
                    activeHandle = handle;
                    e.preventDefault();
                };
                
                const onMouseMove = (e) => {
                    if (!isDragging || !activeHandle) return;
                    
                    const timelineRect = this.timeline.getBoundingClientRect();
                    let newX = e.clientX - timelineRect.left;
                    
                    // 限制在时间轴范围内
                    newX = Math.max(0, Math.min(newX, timelineRect.width));
                    
                    // 确保开始手柄在结束手柄左侧
                    if (activeHandle === this.startHandle) {
                        const endX = parseInt(this.endHandle.style.left) || timelineRect.width;
                        newX = Math.min(newX, endX - 10);
                    } else if (activeHandle === this.endHandle) {
                        const startX = parseInt(this.startHandle.style.left) || 0;
                        newX = Math.max(newX, startX + 10);
                    }
                    
                    activeHandle.style.left = `${newX}px`;
                    updateTimeValues();
                };
                
                const onMouseUp = () => {
                    isDragging = false;
                    activeHandle = null;
                };
                
                // 绑定事件
                this.startHandle.addEventListener('mousedown', (e) => onMouseDown(e, this.startHandle));
                this.endHandle.addEventListener('mousedown', (e) => onMouseDown(e, this.endHandle));
                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
                
                // 初始化手柄位置
                this.startHandle.style.left = '0px';
                this.endHandle.style.left = `${this.timeline.offsetWidth}px`;
            }
            
            loadVideoFromUrl() {
                const url = this.videoUrlInput.value.trim();
                if (!url) {
                    this.showStatus(this.extractionStatus, '请输入视频URL', 'error');
                    return;
                }
                
                this.videoPlayer.src = url;
                this.showStatus(this.extractionStatus, '视频加载中...', 'success');
                
                this.videoPlayer.addEventListener('loadeddata', () => {
                    this.showStatus(this.extractionStatus, '视频加载成功！', 'success');
                    this.setupTimeline();
                });
                
                this.videoPlayer.addEventListener('error', () => {
                    this.showStatus(this.extractionStatus, '视频加载失败，请检查URL', 'error');
                });
            }
            
            loadVideoFromFile(file) {
                if (!file || !file.type.startsWith('video/')) {
                    this.showStatus(this.extractionStatus, '请选择有效的视频文件', 'error');
                    return;
                }
                
                const url = URL.createObjectURL(file);
                this.videoPlayer.src = url;
                this.showStatus(this.extractionStatus, '视频加载中...', 'success');
                
                this.videoPlayer.addEventListener('loadeddata', () => {
                    this.showStatus(this.extractionStatus, '视频加载成功！', 'success');
                    this.setupTimeline();
                });
            }
            
            setupTimeline() {
                // 设置时间轴范围
                const duration = this.videoPlayer.duration;
                this.endTime.textContent = this.formatTime(duration);
                this.endTimeValue = duration;
                
                // 更新结束手柄位置
                this.endHandle.style.left = `${this.timeline.offsetWidth}px`;
            }
            
            updateTimelineProgress() {
                const currentTime = this.videoPlayer.currentTime;
                const duration = this.videoPlayer.duration;
                
                if (duration) {
                    const progress = (currentTime / duration) * 100;
                    this.timelineProgress.style.width = `${progress}%`;
                }
            }
            
            extractFrames() {
                if (!this.videoPlayer.src) {
                    this.showStatus(this.extractionStatus, '请先加载视频', 'error');
                    return;
                }
                
                const interval = parseInt(this.frameInterval.value) || 100;
                const startTime = this.startTimeValue;
                const endTime = this.endTimeValue;
                
                if (startTime >= endTime) {
                    this.showStatus(this.extractionStatus, '结束时间必须大于开始时间', 'error');
                    return;
                }
                
                this.showStatus(this.extractionStatus, '正在提取帧...', 'success');
                
                // 使用VideoFrameExtractor提取帧
                this.frameExtractor.extractFrames(
                    this.videoPlayer,
                    startTime,
                    endTime,
                    interval
                ).then(frames => {
                    this.extractedFrames = frames;
                    this.displayFrames(frames);
                    this.showStatus(this.extractionStatus, `成功提取 ${frames.length} 帧`, 'success');
                }).catch(error => {
                    console.error('帧提取失败:', error);
                    this.showStatus(this.extractionStatus, '帧提取失败', 'error');
                });
            }
            
            displayFrames(frames) {
                this.framesContainer.innerHTML = '';
                
                frames.forEach((frame, index) => {
                    const frameItem = document.createElement('div');
                    frameItem.className = 'frame-item';
                    frameItem.innerHTML = `
                        <img src="${frame}" alt="Frame ${index}" style="width:100%;height:100%;object-fit:cover;">
                        <div class="frame-checkbox" data-index="${index}"></div>
                    `;
                    
                    frameItem.addEventListener('click', (e) => {
                        if (e.target.classList.contains('frame-checkbox') || e.target === frameItem) {
                            this.toggleFrameSelection(index);
                        }
                    });
                    
                    this.framesContainer.appendChild(frameItem);
                });
                
                this.updateSelectedCount();
            }
            
            toggleFrameSelection(index) {
                const frameItem = this.framesContainer.children[index];
                const checkbox = frameItem.querySelector('.frame-checkbox');
                
                if (this.selectedFrames.includes(index)) {
                    // 取消选择
                    this.selectedFrames = this.selectedFrames.filter(i => i !== index);
                    frameItem.classList.remove('selected');
                    checkbox.classList.remove('checked');
                } else {
                    // 选择
                    this.selectedFrames.push(index);
                    frameItem.classList.add('selected');
                    checkbox.classList.add('checked');
                }
                
                this.updateSelectedCount();
            }
            
            selectAllFrames() {
                this.selectedFrames = Array.from({length: this.extractedFrames.length}, (_, i) => i);
                
                Array.from(this.framesContainer.children).forEach((frameItem, index) => {
                    frameItem.classList.add('selected');
                    frameItem.querySelector('.frame-checkbox').classList.add('checked');
                });
                
                this.updateSelectedCount();
            }
            
            deselectAllFrames() {
                this.selectedFrames = [];
                
                Array.from(this.framesContainer.children).forEach(frameItem => {
                    frameItem.classList.remove('selected');
                    frameItem.querySelector('.frame-checkbox').classList.remove('checked');
                });
                
                this.updateSelectedCount();
            }
            
            clearAllFrames() {
                this.extractedFrames = [];
                this.selectedFrames = [];
                this.framesContainer.innerHTML = '';
                this.updateSelectedCount();
                this.stopAnimation();
            }
            
            updateSelectedCount() {
                this.selectedCount.textContent = this.selectedFrames.length;
            }
            
            playAnimation() {
                if (this.selectedFrames.length === 0) {
                    this.showStatus(this.exportStatus, '请先选择要播放的帧', 'error');
                    return;
                }
                
                const frames = this.selectedFrames.map(index => this.extractedFrames[index]);
                const speed = parseFloat(this.animationSpeed.value);
                
                this.frameAnimation.play(frames, speed);
            }
            
            stopAnimation() {
                this.frameAnimation.stop();
            }
            
            exportFrames() {
                if (this.selectedFrames.length === 0) {
                    this.showStatus(this.exportStatus, '请先选择要导出的帧', 'error');
                    return;
                }
                
                const prefix = this.exportPrefix.value || 'frame';
                const frames = this.selectedFrames.map(index => ({
                    dataUrl: this.extractedFrames[index],
                    index: index
                }));
                
                try {
                    this.frameExporter.exportFrames(frames, prefix);
                    this.showStatus(this.exportStatus, `成功导出 ${frames.length} 帧`, 'success');
                } catch (error) {
                    this.showStatus(this.exportStatus, '导出失败: ' + error.message, 'error');
                }
            }
            
            showStatus(element, message, type) {
                element.textContent = message;
                element.className = `status ${type}`;
                
                // 3秒后隐藏状态消息
                setTimeout(() => {
                    element.style.display = 'none';
                }, 3000);
            }
            
            formatTime(seconds) {
                const mins = Math.floor(seconds / 60);
                const secs = Math.floor(seconds % 60);
                return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
            }
        }
    </script>
</body>
</html>